﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Filtry.Utilities
{
    public static class Tools
    {
        /// <summary>
        /// </summary>
        /// <param name="random"></param>
        /// <returns></returns>
        public static double NextLaplaceDouble(this Random random)
        {
            return NextLaplaceDouble(random, 1, 0);
        }

        /// <summary>
        /// </summary>
        /// <param name="random"></param>
        /// <param name="lambda"></param>
        /// <param name="mean"></param>
        /// <returns></returns>
        public static double NextLaplaceDouble(this Random random, double lambda, double mean)
        {
            int sign = random.NextDouble() < 0.5 ? -1 : 1;
            return mean - lambda * sign * Math.Log(1 - 2 * (random.NextDouble() - 0.5));
        }

        /// <summary>
        /// </summary>
        /// <param name="random"></param>
        /// <returns></returns>
        public static double NextNormalDouble(this Random random)
        {
            return NextNormalDouble(random, 1, 0);
        }

        /// <summary>
        /// </summary>
        /// <param name="random"></param>
        /// <param name="deviation"></param>
        /// <param name="mean"></param>
        /// <returns></returns>
        public static double NextNormalDouble(this Random random, double deviation, double mean)
        {
            double u;
            double s;
            do
            {
                u = random.NextDouble() * 2 - 1;
                double v = random.NextDouble() * 2 - 1;
                s = u * u + v * v;
            } while (s >= 1);

            return mean + deviation * u * Math.Sqrt(-2.0 * Math.Log(s) / s);
        }

        /// <summary>
        /// </summary>
        /// <param name="random"></param>
        /// <param name="bytes"></param>
        /// <param name="deviation"></param>
        /// <param name="mean"></param>
        public static void NormalBytes(this Random random, double[] bytes, double deviation = 1, double mean = 0)
        {
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = NextNormalDouble(random, deviation, mean);
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="deviation"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public static double[] CalculateNormalizedKernel(double deviation, int size)
        {
            double[] kernel = CalculateKernel(deviation, size);
            return NormalizeKernel(kernel);
        }

        /// <summary>
        ///     Calculates 1D Gaussian kernel with specific size and deviation.
        /// </summary>
        /// <param name="deviation"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public static double[] CalculateKernel(double deviation, int size)
        {
            var kernel = new double[size];
            int half = size / 2;
            for (int i = 0; i < size; i++)
            {
                kernel[i] = 1 / (Math.Sqrt(2 * Math.PI) * deviation) * Math.Exp(-(i - half) * (i - half) / (2 * deviation * deviation));
            }
            return kernel;
        }

        /// <summary>
        ///     Calculates R kernel.
        /// </summary>
        /// <param name="bytePixels"></param>
        /// <param name="deviation"></param>
        /// <param name="pixelValue"></param>
        /// <returns></returns>
        public static double[] CalculateKernelR(double[] bytePixels, double deviation, double pixelValue)
        {
            var kernel = new double[bytePixels.Length];

            for (int i = 0; i < bytePixels.Length; i++)
            {
                kernel[i] = 1 / (Math.Sqrt(2 * Math.PI) * deviation) *
                            Math.Exp(-(bytePixels[i] - pixelValue) / 255 * (bytePixels[i] - pixelValue) / 255 /
                                     (2 * deviation * deviation));
            }
            return kernel;
        }

        /// <summary>
        /// </summary>
        /// <param name="kernel"></param>
        /// <returns></returns>
        public static double[] NormalizeKernel(double[] kernel)
        {
            double sum = kernel.Sum();
            for (int i = 0; i < kernel.Length; i++)
            {
                kernel[i] = kernel[i] / sum;
            }
            return kernel;
        }


        private static long[] Substract(this byte[] aByteArray, byte[] bByteArray)
        {
            if (aByteArray.Length != bByteArray.Length)
            {
                return null;
            }
            var result = new long[aByteArray.Length];
            for (int i = 0; i < aByteArray.Length; i++)
            {
                result[i] = (aByteArray[i] - bByteArray[i]);
            }
            return result;
        }

        private static double[] Substract(this IEnumerable<byte> aBytes, double value)
        {
            return aBytes.Select(b => b - value).ToArray();
        }

        public static long[] CalculateDistance(byte[] aArray, byte[] bArray, Metric metric, ImgType imgType)
        {
            long[] difference = aArray.Substract(bArray);
            if (imgType == ImgType.Gray)
            {
                difference = difference.Select(Math.Abs).ToArray();
            }
            else
            {
                switch (metric)
                {
                    case Metric.Euklidean:
                        break;
                    case Metric.Manhattan:
                        break;
                    case Metric.Maximum:
                        break;
                }
            }
            return difference;
        }

        public static double[] CalculateDistance(byte[] aBytes, double value, Metric metric, ImgType imgType)
        {
            var difference = aBytes.Substract(value);
         
            if (imgType == ImgType.Gray)
            {
                difference = difference.Select(Math.Abs).ToArray();
            }

            return difference;
        }
   
    }

    public enum ImgType
    {
        Gray,
        RGB
    }

    public enum Metric
    {
        Euklidean,
        Manhattan,
        Maximum
    }
}